﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using Newtonsoft.Json;
using Xenta.Attributes;
using Xenta.Enums;
using Xenta.Operations;
using Xenta.Utils;

namespace Xenta.Api
{
    /// <summary>
    /// Represents the operation service.
    /// </summary>
    [Dependency("4ee2accb-d305-4419-92de-f4420ee15d4f",
        Lifestyle = DependencyLifestyle.Singleton)]
    public class OperationService : IHandler
    {
        #region Constants

        private static readonly JsonSerializerSettings JSON_SETTINGS = new JsonSerializerSettings
        {
            Formatting = Formatting.None,
            NullValueHandling = NullValueHandling.Ignore,
            Culture = CultureInfo.InvariantCulture
        };

        #endregion

        #region Fields

        private readonly IInfrastructure _infrastructure;

        #endregion

        #region Ctors

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="infrastructure">The infrastructure.</param>
        public OperationService(IInfrastructure infrastructure)
        {
            _infrastructure = infrastructure;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Handles the input stream, using the ingoing properties, 
        /// and returns the result to the output stream, filling 
        /// the outgoing properties.
        /// </summary>
        /// <param name="mimeType">The MIME type string reference.</param>
        /// <param name="inProperties">The ingoing properties.</param>
        /// <param name="inStream">The input stream.</param>
        /// <param name="outProperties">The outgoing properties.</param>
        /// <param name="outStream">The output stream.</param>
        /// <remarks>
        /// The handler should not dispose the provided streams.
        /// </remarks>
        public void Handle(ref string mimeType,
            Dictionary<String, String> inProperties,
            Stream inStream,
            Dictionary<String, String> outProperties,
            Stream outStream)
        {
            if(!mimeType.Equals("application/json"))
                throw Err.Of(this).NotValid("mimeType").Ex();
            if(!inProperties.ContainsKey("OperationName"))
                throw Err.Of(this).NotSpecified("OperationName").Ex();
            if(!inProperties.ContainsKey("CID"))
                throw Err.Of(this).NotSpecified("CID").Ex();

            var cid = inProperties["CID"].ParseAs<Guid>();
            if(cid == Guid.Empty)
                throw Err.Of(this).NotValid("CID").Ex();
            var sid = inProperties.ContainsKey("SID")
                ? inProperties["SID"].ParseAs<Guid>()
                : Guid.Empty;

            string operationName = inProperties["OperationName"];
            if(String.IsNullOrWhiteSpace(operationName))
                throw Err.Of(this).NotValid("OperationName").Ex();
            if(!_infrastructure.CheckDependency<IApiOperation>(operationName))
                throw Err.Of<IApiOperation>().NotFound().Ex();

            using(var ioc = _infrastructure.DependencyResolver())
            {
                var operation = ioc.Resolve(operationName)
                    as IApiOperation;

                if(inStream.Length != 0)
                {
                    using(var streamReader = new StreamReader(inStream, 
                        Encoding.Unicode, 
                        true, 
                        1024, 
                        true))
                    {
                        using(var jsonReader = new JsonTextReader(streamReader)
                        {
                            CloseInput = false
                        })
                        {
                            JsonSerializer.Create(JSON_SETTINGS).Populate(jsonReader, operation);
                        }
                    }
                }

                var processor = ioc.Resolve<IOperationProcessor>().Init(new WorkingContext
                {
                    CID = cid,
                    SID = sid,
                    IsSafe = false
                });

                try
                {
                    processor.Execute(operation);
                    if(operation is IDisclosable)
                    {
                        var level = inProperties.ContainsKey("DisclosureLevel")
                            ? inProperties["DisclosureLevel"].ParseAs(DisclosureLevel.Primary)
                            : DisclosureLevel.Primary;
                        string optionStr = inProperties.ContainsKey("DisclosureOptions")
                            ? inProperties["DisclosureOptions"]
                            : String.Empty;
                        var options = new Dictionary<String, String>().Load(optionStr);
                        var dto = (operation as IDisclosable).Disclose(level, options);
                        if(dto != null)
                        {
                            using(var streamWriter = new StreamWriter(outStream, 
                                Encoding.Unicode,
                                1024,
                                true))
                            {
                                using(var jsonWriter = new JsonTextWriter(streamWriter)
                                {
                                    CloseOutput = false
                                })
                                {
                                    JsonSerializer.Create(JSON_SETTINGS).Serialize(jsonWriter, new
                                    {
                                        Result = dto
                                    });
                                }
                            }
                        }
                    }
                    processor.Commit();
                }
                catch
                {
                    processor.Rollback();
                    throw;
                }
            }
        }

        #endregion
    }

    /// <summary>
    /// Exposes the API opertaion interface.
    /// </summary>
    public interface IApiOperation : IOperation
    {
    }
}
